/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// Z_zone.c

#include "quakedef.h"

QHEAP *zoneHeap = NULL;
QPOOL *mapPool = NULL;
QPOOL *gamePool = NULL;
QSCRATCH *qScratch = NULL;

/*
========================
Memory_Init
========================
*/
void Memory_Init (void)
{
	zoneHeap = new QHEAP ();				// unbounded
	mapPool = new QPOOL (0x10000000);		// 256mb
	gamePool = new QPOOL (0x10000000);		// 256mb
	qScratch = new QSCRATCH (0x4000000);	// 64mb
}


/*
===============================================================================

POOL MEMORY

===============================================================================
*/

QPOOL::QPOOL (int maxsize)
{
	// round to 1mb boundaries and add an extra 1mb to protect against partial overflow
	this->size = 0x100000 + ((maxsize + 0xfffff) & ~0xfffff);
	this->lowmark = 0;
	this->highmark = 0;

	if ((this->base = (byte *) VirtualAlloc (NULL, this->size, MEM_RESERVE, PAGE_NOACCESS)) == NULL)
	{
		Sys_Error ("PoolCreate - VirtualAlloc failed on memory pool");
		return;
	}

	// commit an initial 1mb
	this->Alloc (0x100000);
	this->FreeToLowMark (0);
}


QPOOL::~QPOOL (void)
{
	VirtualFree (this->base, this->size, MEM_DECOMMIT);
	VirtualFree (this->base, 0, MEM_RELEASE);
}


void *QPOOL::FastAlloc (int size)
{
	// round to 16 byte boundary
	size = (size + 15) & ~15;

	// we can't align the pointer because edicts need an explicit size so just take it direct
	if (this->lowmark + size >= this->size)
	{
		Sys_Error ("PoolAlloc - overflow on memory pool");
		return NULL;
	}

	// size might be > the extra alloc size
	if ((this->lowmark + size) > this->highmark)
	{
		// round to 1MB boundaries
		this->highmark = (this->lowmark + size + 0xfffff) & ~0xfffff;

		// this will walk over a previously committed region.  i might fix it...
		if (!VirtualAlloc (&this->base[this->lowmark], this->highmark - this->lowmark, MEM_COMMIT, PAGE_READWRITE))
		{
			Sys_Error ("PoolAlloc - VirtualAlloc failed for memory pool");
			return NULL;
		}
	}

	// fix up pointers and return what we got
	byte *buf = &this->base[this->lowmark];

	// and advance the lowmark
	this->lowmark += size;

	return buf;
}


void *QPOOL::Alloc (int size)
{
	// round to 16 byte boundary
	size = (size + 15) & ~15;

	void *buf = this->FastAlloc (size);

	// ensure clear to zero
	memset (buf, 0, size);

	return buf;
}


int QPOOL::GetLowMark (void)
{
	return this->lowmark;
}


void QPOOL::FreeToLowMark (int mark)
{
	// can't free to >= current low mark
	if (mark < this->lowmark)
	{
		memset (&this->base[mark], 0, this->lowmark - mark);

		// if freeing to 0 we do a full decommit
		if (mark == 0)
		{
			VirtualFree (this->base, this->highmark, MEM_DECOMMIT);
			this->highmark = 0;
		}

		this->lowmark = mark;
	}
}


/*
===============================================================================

SCRATCH MEMORY

===============================================================================
*/

QSCRATCH::QSCRATCH (int maxsize)
{
	// round to 1mb boundaries and add an extra 1mb to protect against partial overflow
	this->size = 0x100000 + ((maxsize + 0xfffff) & ~0xfffff);
	this->lowmark = 0;
	this->highmark = 0;

	this->ScratchHeap = new QHEAP ();
	this->base = (byte *) this->ScratchHeap->Alloc (this->size);
	this->Locked = FALSE;
}


QSCRATCH::~QSCRATCH (void)
{
	this->ScratchHeap->Free (this->base);
	delete this->ScratchHeap;
	this->ScratchHeap = NULL;
	this->base = NULL;
}


void *QSCRATCH::Lock (char *caller)
{
	// check if the scratch pool was locked and if so we error out
	if (this->Locked)
	{
		Sys_Error ("QSCRATCH::Lock : attempt to lock pool but pool was already locked (by %s)", this->LockedFunction);
		return NULL;
	}

	// mark as locked so nothing can access it until we unlock it
	this->Locked = TRUE;
	strcpy (this->LockedFunction, caller);

	// and hand back the pointer to it's current base memory
	return &this->base[this->lowmark];
}


void QSCRATCH::Unlock (void)
{
	// just unlock the pool
	this->Locked = FALSE;
}


void *QSCRATCH::FastAlloc (int size)
{
	// check if the scratch pool was locked and if so we error out
	if (this->Locked)
	{
		Sys_Error ("QSCRATCH::FastAlloc : attempt to allocate %i bytes while the pool was locked by %s", size, this->LockedFunction);
		return NULL;
	}

	// for allocations that last the lifetime of one function (or until the next alloc); does not advance the lowmark
	if (this->lowmark + size >= this->size)
	{
		Sys_Error ("QSCRATCH::TempAlloc - overflow on memory pool");
		return NULL;
	}

	byte *buf = &this->base[this->lowmark];

	this->lowmark += size;

	return buf;
}


void *QSCRATCH::Alloc (int size)
{
	void *buf = this->FastAlloc (size);
	memset (buf, 0, size);

	return buf;
}


int QSCRATCH::GetLowMark (void)
{
	return this->lowmark;
}


void QSCRATCH::FreeToLowMark (int mark)
{
	if (mark < 0)
		this->lowmark = 0;
	else if (mark < this->size)
		this->lowmark = mark;
}


void QSCRATCH::AdvanceLowMark (int advance)
{
	// allow advancing the lowmark from variable sized temp allocs, e.g. in va
	this->lowmark += advance;
}


// this automatically locks and unlocks the scratch when it goes out of scope
// so that i don't have to do so myself nor worry about early exits/etc
QSCRATCHLOCKER::QSCRATCHLOCKER (void **data, char *caller)
{
	*data = qScratch->Lock (caller);
}


QSCRATCHLOCKER::~QSCRATCHLOCKER (void)
{
	qScratch->Unlock ();
}


/*
===============================================================================

HEAP MEMORY

===============================================================================
*/

QHEAP::QHEAP (void)
{
	this->hHeap = NULL;
	this->CreateHeap ();
}


QHEAP::~QHEAP (void)
{
	assert (this->hHeap != NULL);
	this->Discard ();
}


void *QHEAP::Alloc (int size)
{
	assert (this->hHeap != NULL);
	return HeapAlloc (this->hHeap, HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, size);
}


void *QHEAP::FastAlloc (int size)
{
	assert (this->hHeap != NULL);
	return HeapAlloc (this->hHeap, HEAP_GENERATE_EXCEPTIONS, size);
}


void QHEAP::Free (void *data)
{
	if (data)
	{
		assert (this->hHeap != NULL);
		HeapFree (this->hHeap, 0, data);
	}
}


void QHEAP::Discard (void)
{
	assert (this->hHeap != NULL);
	HeapDestroy (this->hHeap);
	this->hHeap = NULL;
}


void QHEAP::CreateHeap (void)
{
	assert (this->hHeap == NULL);
	this->hHeap = HeapCreate (0, 0, 0);
	assert (this->hHeap != NULL);
}

